﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CoreModel = PlayerScannerCore.Model;
using Mvvm;
using System.Collections.ObjectModel;
using PlayerScannerCore.Properties;

namespace PlayerScannerCore.ViewModel
{
    public enum PlayerViewScope { Inventory = 0, Techs = 1, Stats = 2 }

    public class ViewModel : ViewModelBase
    {
        private const string TITLE = "Player Scanner";

        private CoreModel.Model _model;
        private bool _isAttached;
        private ObservableCollection<PlayerViewModel> _playerList = new ObservableCollection<PlayerViewModel>();
        private PlayerViewModel _selectedPlayerViewModel;
        private string _leaderName;
        private string _leaderSectionId;

        public ViewModel()
        {
            _model = new CoreModel.Model();

            //Setup the factories for the application
            Factory.Instance.ItemFactory = new ViewModelItemFactory();
            Factory.Instance.PlayerFactory = new ViewModelPlayerFactory(_model);
            
            FloorViewModel = new FloorViewModel(_model);
        }

        public string LeaderName
        {
            get { return _leaderName; }
            set
            {
                if (_leaderName != value)
                {
                    _leaderName = value;
                    OnPropertyChanged("LeaderName");
                }
            }
        }
        public string LeaderSectionId
        {
            get { return _leaderSectionId; }
            set
            {
                if (_leaderSectionId != value)
                {
                    _leaderSectionId = value;
                    OnPropertyChanged("LeaderSectionId");
                }
            }
        }

        public PlayerViewModel SelectedPlayer
        {
            get { return _selectedPlayerViewModel; }
            set
            {
                if (_selectedPlayerViewModel != value)
                {
                    _selectedPlayerViewModel = value;
                    OnPropertyChanged("SelectedPlayer");

                    //Refresh the newly selected player immediately for better user experience
                    if (_selectedPlayerViewModel != null)
                        _selectedPlayerViewModel.Refresh(false);
                }
            }
        }
        public FloorViewModel FloorViewModel { get; private set; }
        public ObservableCollection<PlayerViewModel> PlayerList
        {
            get { return _playerList; }
            set
            {
                if (_playerList != value)
                {
                    _playerList = value;
                    OnPropertyChanged("PlayerList");
                }
            }
        }

        /// <summary>
        /// Gets the title of the top-level container
        /// </summary>
        public string Title
        {
            get { return TITLE; }
        }

        /// <summary>
        /// Gets or sets a value that determines whether or not the application is attached 
        /// to PSO.
        /// </summary>
        public bool IsAttached
        {
            get { return _isAttached; }
            set
            {
                if (_isAttached != value)
                {
                    _isAttached = value;
                    OnPropertyChanged("IsAttached");
                }
            }
        }

        /// <summary>
        /// Causes this view model and all child view models to update themselves.
        /// </summary>
        public void Refresh()
        {
            //The model automatically detatches from pso when it's handle becomes invalid. This 
            //method doesn't throw exceptions.
            if (!_model.IsAttached)
                _model.AttachToPso();

            IsAttached = _model.IsAttached;

            if (IsAttached)
            {
                try
                {
                    //Update room information
                    var headerInfo = _model.GetRoomInfo();
                    LeaderName = headerInfo.LeaderName;
                    LeaderSectionId = headerInfo.LeaderSectionId;

                    //Update the player list
                    var newPlayerList = _model.GetPlayerList();

                    //Remove old players
                    var playersToRemove = new List<IPlayerIdentifier>();
                    foreach (var player in PlayerList)
                    {
                        if (newPlayerList.FirstOrDefault(item => item.Index == player.Index && item.PlayerName == player.PlayerName) == null)
                            playersToRemove.Add(player);
                    }
                    foreach (PlayerViewModel player in playersToRemove)
                        PlayerList.Remove(player);

                    //Add new players
                    foreach (PlayerViewModel player in newPlayerList)
                    {
                        var foundPlayer = PlayerList.FirstOrDefault(item => item.Index == player.Index && item.PlayerName == player.PlayerName);
                        if (foundPlayer == null)
                            PlayerList.Add(player);
                    }

                    //Auto-select the first player if one hasn't been selected yet
                    if (PlayerList.Count > 0 && SelectedPlayer == null)
                        SelectedPlayer = PlayerList[0];

                    //Refresh all the players in the list, applying only basic info update for 
                    //unselected players
                    foreach (var player in PlayerList)
                        player.Refresh(player != SelectedPlayer);

                }
                catch (Exception) { } //Ignore all exceptions. The next iteration will try again.
                //It's assumed that any errors are temporary memory read errors caused by reading 
                //pointer chains before they've been fully established or errors caused by closing 
                //PSO while this application is running. Either way, these sorts of errors are 
                //unavoidable due to the the nature of remote process pointer chain traversal.
            }
        }
    }
}
